package com.tvunetworks.center.device.service.impl;

import com.fasterxml.jackson.databind.JsonNode;
import com.tvunetworks.center.common.constant.LocalConstant;
import com.tvunetworks.center.common.exception.ServerException;
import com.tvunetworks.center.common.util.MyUtil;
import com.tvunetworks.center.common.util.Result;
import com.tvunetworks.center.device.constant.CommandConstant;
import com.tvunetworks.center.device.mapper.PidPortMappingMapper;
import com.tvunetworks.center.device.model.PidPortMapping;
import com.tvunetworks.center.device.model.PidPortMappingRecord;
import com.tvunetworks.center.device.model.result.ResultConstant;
import com.tvunetworks.center.device.model.vo.PidPortMappingVo;
import com.tvunetworks.center.device.service.DeviceService;
import com.tvunetworks.center.device.service.PidPortMappingRecordService;
import com.tvunetworks.center.device.service.PidPortMappingService;
import com.tvunetworks.center.device.service.ReceiverControlService;
import com.tvunetworks.center.device.service.feign.TokenFeignService;
import com.tvunetworks.center.device.service.rpc.InstanceRpcService;
import com.tvunetworks.center.device.util.RedisLock;
import com.tvunetworks.center.device.util.StringUtil;
import com.tvunetworks.center.gs.model.Device;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author Etan Yang
 * @create: 2021/10/13 15:29
 */

@Slf4j
@Service
public class PidPortMappingServiceImpl extends BaseService<PidPortMapping> implements PidPortMappingService  {
    /**
     * pidPortMappingCacheMap
     */
    public  static final   Map<String, PidPortMapping> PID_PORT_MAPPING_CACHE_MAP = new ConcurrentHashMap<>();
    @Autowired
    private DeviceService deviceService;
    @Autowired
    private PidPortMappingMapper pidPortMappingMapper;
    @Autowired
    private InstanceRpcService instanceRpcService;
    @Autowired
    private PidPortMappingRecordService pidPortMappingRecordService;
    @Autowired
    private RedisLock redisLock;
    @Autowired
    private TokenFeignService tokenFeignService;
    @Autowired
    private ReceiverControlService receiverControlService;
    @Value("${instance.cloudR.webR.suffix:/webr}")
    private String cloudWebRSuffix;


    @Value("${enable.notifyReceiverOnline}")
    private boolean enableNotifyReceiverOnline;
    @Value("${receiver.domain.prefix:dev-}")
    private String domainPrefix;
    @Value("${receiver.domain.suffix:T1279}")
    private String domainSuffix;
    @Value("${receiver.sub.domain:.tvustream.com}")
    private String subDomain;
    @Value("${cloud.receiver.prefix:fffffff}")
    private String cloudReceiverPrefix;


    @Override
    public synchronized void initDeviceExistIpToPidPortMapping() {
        //1.获取所有满足的设备R
        List<Device> receivers = deviceService.listReceiverByIpIsNotNull();
        //2.获取数据库中已经存在的receiver
        List<PidPortMapping> existPidPortMappings = super.selectExistAll();
        List<String> tempExistPidPortMappingPids = new ArrayList<>();
        if (!CollectionUtils.isEmpty(existPidPortMappings)) {
            tempExistPidPortMappingPids = existPidPortMappings.stream().map(s->s.getPeerId().toLowerCase()).collect(Collectors.toList());
        }
        //2.将消息全部导入
        if (!CollectionUtils.isEmpty(receivers)) {
            Map<String, Device> deviceMap = receivers.stream().collect(Collectors.toMap(Device::getPeerId, Function.identity(), (s1, s2) -> s2));
            List<String> existPidPortMappingPids = tempExistPidPortMappingPids;
            deviceMap.values().forEach(receiver -> {
                String peerId = receiver.getPeerId();
                if (!existPidPortMappingPids.contains(peerId.toLowerCase())) {
                    PidPortMapping pidPortMapping = new PidPortMapping()
                            .setIp(receiver.getIp())
                            .setPeerId(receiver.getPeerId());
                    super.insert(pidPortMapping);
                }
            });
        }

    }

    @Override
    public int deletePidPortMapping() {
        return pidPortMappingMapper.deletePidPortMapping();
    }

    @Override
    public Map<String, String> initOnlineOrLiveReceiverDomainToPidPortMapping() {
        Map<String, String> result = new HashMap<>();
        int successCount = 0;
        int failedCount = 0;
        StringBuilder successPeerId = new StringBuilder("[");
        StringBuilder failedPeerId = new StringBuilder("[");
        result.put("SUCCESS", String.valueOf(successCount));
        result.put("FAILED", String.valueOf(failedCount));
        //1.获取所有domain为空的设备
        List<PidPortMapping> existPidPortMappings = pidPortMappingMapper.listAllReceiverByDomainIsEmpty();
        if (CollectionUtils.isEmpty(existPidPortMappings)) {
            log.error("initOnlineOrLiveReceiverDomainToPidPortMapping>>>>>>>listAllReceiverByDomainIsEmpty is empty");
            return result;
        }
        //2.获取对应的map
        Map<String, PidPortMapping> pidPortMappingMap = existPidPortMappings.stream().collect(Collectors.toMap(PidPortMapping::getPeerId, Function.identity(), (s1, s2) -> s2));
        //3.根据pid批量获取设备
        List<Device> devices = deviceService.listCurrentDevice(pidPortMappingMap.keySet());
        if (!CollectionUtils.isEmpty(devices)) {
            //4.过滤在线或live的R
            devices = devices.stream().filter(device -> StringUtils.equalsIgnoreCase(device.getStatus(), "1")
                    || StringUtils.equalsIgnoreCase(device.getStatus(), "2")).collect(Collectors.toList());
        }
        if (CollectionUtils.isEmpty(devices)) {
            log.error("initOnlineOrLiveReceiverDomainToPidPortMapping>>>>>>>device is empty,existPidPortMappings:[{}]", existPidPortMappings);
            return result;

        }
        //5.批量调用instance接口
        devices.forEach(device -> {
            String peerId = device.getPeerId();
            //5.1.根据pid获取pidPortMapping对象
            PidPortMapping pidPortMapping = pidPortMappingMap.get(peerId);
            if (pidPortMapping == null) {
                log.error("initOnlineOrLiveReceiverDomainToPidPortMapping>>>>>>pidPortMappingMap get pid is empty,pid:[{}]", peerId);
                return;
            }
            try {
                String domain = this.getAndParseAndUpdateDomain(peerId, device.getIp(), pidPortMapping, Boolean.FALSE);
                if (domain == null) {
                    log.error("initOnlineOrLiveReceiverDomainToPidPortMapping>>>>>>addDomainRecord exception, rid:[{}],ip:[{}]",
                            peerId, pidPortMapping.getIp());
                    result.put("FAILED", String.valueOf(Integer.parseInt(result.get("FAILED")) + 1));
                    failedPeerId.append(peerId).append(",");
                    return;
                }
                log.error("initOnlineOrLiveReceiverDomainToPidPortMapping>>>>>>SUCCESS rid:[{}],ip:[{}],domain:[{}]",
                        peerId, pidPortMapping.getIp(), domain);
                result.put("SUCCESS", String.valueOf(Integer.parseInt(result.get("SUCCESS")) + 1));
                successPeerId.append(peerId).append(",");
            } catch (NumberFormatException e) {
                log.error("initOnlineOrLiveReceiverDomainToPidPortMapping>>>>>>Exception rid:[{}],ip:[{}],exception:[{}]",
                        peerId, pidPortMapping.getIp(), e);
                result.put("FAILED", String.valueOf(Integer.parseInt(result.get("FAILED")) + 1));
                failedPeerId.append(peerId).append(",");
            }
            //睡500ms继续调用
            try {
                Thread.sleep(500);
            } catch (Exception e) {
                log.error("initOnlineOrLiveReceiverDomainToPidPortMapping>>>>Sleep 500 time exception,pid:[{}],e:[{}]", peerId, e);
            }
        });
        successPeerId.append("]");
        failedPeerId.append("]");
        result.put("SuccessPeerIds", successPeerId.toString());
        result.put("FailedPeerIds", failedPeerId.toString());
        return result;
    }

    /**
     * 1.根据MD5(前缀+peerId+后缀)+二级和一级域名生成域名
     *
     * @param peerId         设备的id
     * @param ip             设备的ip
     * @param pidPortMapping 设备的域名或ip信息
     * @param cloudReceiverFlag true表示cloudR也重新添加映射，fasle表示cloudR不添加
     * @return 返回domain，如果解析失败则返回null
     */
    private String getAndParseAndUpdateDomain(String peerId, String ip, PidPortMapping pidPortMapping, boolean cloudReceiverFlag) {
        //1.根据pid获取domain
        String domain = this.getDomainByPeerId(peerId);
        String result = domain;
        if (cloudReceiverFlag || !StringUtils.startsWith(peerId, this.cloudReceiverPrefix)) {
            //2.instance服务ip与域名映射
            if (!instanceRpcService.getDomainByPeerIdAndIp(domain, ip, 0)) {
                log.error("initOnlineOrLiveReceiverDomainToPidPortMapping>>>>>>addDomainRecord exception, rid:[{}],ip:[{}],domain:[{}]",
                        peerId, pidPortMapping.getIp(), domain);
                result = null;
            }
        }
        domain = domain + this.subDomain;
        String oldIp = pidPortMapping.getIp();
        Long createTime = pidPortMapping.getCreateTime();
        try {
            //3.更新pidPortMapping
            pidPortMapping.setSubDomain(domain);
            pidPortMapping.setIp(ip);
            super.updateSelective(pidPortMapping);
            //4.更新缓存
            PID_PORT_MAPPING_CACHE_MAP.put(peerId, pidPortMapping);
        } catch (Exception e) {
            log.error("getAndParseAndUpdateDomain>>>>>>>> exception peerId:[{}],ip:[{}],e:[{}]", peerId, ip, e);
            pidPortMapping.setIp(oldIp);
        }
        pidPortMapping.setCreateTime(createTime);
        return result;
    }

    @Override
    public void receiverStatusChanged(String message) {
        //1.解析message并获取device
        Device device = this.parseMessageAndGetPeerId(message);
        if (device == null || StringUtils.isEmpty(device.getIp())) {
            log.info("receiverStatusChanged>>>>>>>>parseMessageAndGetPeerId peerId is null or ip is empty,message:[{}],device:[{}]", message, device);
            return;
        }
        String peerId = device.getPeerId();
        String ip = device.getIp();
        //2.根据peerId获取PidPortMapping对象
        PidPortMapping pidPortMapping = this.getPortMappingCacheByPeerId(peerId, ip);
        //3.判断Ip是否有变化
        if (!StringUtils.equalsIgnoreCase(ip, pidPortMapping.getIp())) {
            this.ipChanged(peerId, ip, pidPortMapping, "Receiver Report", Boolean.FALSE);
            return;
        }
        //4.ip未变，domain为空
        if (StringUtils.isEmpty(pidPortMapping.getSubDomain())) {
            PidPortMapping byPeerId = pidPortMappingMapper.getByPeerId(peerId);
            if (byPeerId == null || StringUtils.isEmpty(byPeerId.getSubDomain())) {
                this.getAndParseAndUpdateDomain(peerId, ip, pidPortMapping, Boolean.FALSE);
            }else {
                log.error("pidPortMapping.getSubDomain() is not empty in db,pidPortMapping:[{}]", byPeerId);
                pidPortMapping.setSubDomain(byPeerId.getSubDomain());
            }
        }
    }

    /**
     * 当ip改变时，需要先删除domain再更新domain
     *
     * @param peerId
     * @param ip
     * @param pidPortMapping
     * @param remark
     * @param cloudReceiverFlag true表示cloudR也重新添加映射，fasle表示cloudR不添加
     * @return
     */
    private String ipChanged(String peerId, String ip, PidPortMapping pidPortMapping, String remark, boolean cloudReceiverFlag) {
        //1.删除domain的映射关系
        if (cloudReceiverFlag || !StringUtils.startsWith(peerId, this.cloudReceiverPrefix)) {
            String domain = this.getDomainByPeerId(peerId);
            if (!instanceRpcService.releaseDomainByPeerId(domain)) {
                log.error("ipChanged>>>>>releaseDomainByPeerId failed,peerId:[{}],domain:[{}],ip:[{}]", peerId, domain, ip);
                return "releaseFailed";
            }
            String oldIp = pidPortMapping.getIp();
            domain = this.getAndParseAndUpdateDomain(peerId, ip, pidPortMapping, cloudReceiverFlag);
            //6.如果ip改变了，则需要添加到t_pid_port_mapping_record表
            if (StringUtils.isNotEmpty(domain)) {
                try {
                    PidPortMappingRecord pidPortMappingRecord = new PidPortMappingRecord();
                    BeanUtils.copyProperties(pidPortMapping, pidPortMappingRecord);
                    pidPortMappingRecord.setSubDomainStartTime(pidPortMapping.getUpdateTime())
                            .setSubDomainEndTime(System.currentTimeMillis())
                            .setRemark(remark)
                            .setIp(oldIp);
                    pidPortMappingRecordService.add(pidPortMappingRecord);
                } catch (BeansException e) {
                    log.error("receiverStatusChanged>>>>>addPidPortMappingRecord exception,pidPortMapping:[{}],e:[{}]", pidPortMapping, e);
                }
            }
            return domain;
        }
        return "SUCCESS";
    }

    /**
     * 解析message并获取满足条件的peerId
     * 条件：1.必须为R
     * 2.必须满足以下条件之一
     * 2.1.要么包含IP
     * 2.2.要么必须由离线到在线
     * @param message MQ中的上报信息
     * @return 返回device，如果不满足条件，则返回null
     */
    private Device parseMessageAndGetPeerId(String message) {
        try {
            //1.获取接送Node
            JsonNode jsonNode = MyUtil.MAPPER.readTree(message);
            //2.解析jsonNode
            //2.1.查看是否包括peerId
            if (!jsonNode.has("peerId")) {
                log.error("parseMessageAndGetPeerId failed message not contain peerId message:{}", message);
                return null;
            }
            //2.2.查看是否包括data
            if (!jsonNode.has("data")) {
                log.error("parseMessageAndGetPeerId failed message not contain data message:{}", message);
                return null;
            }
            //3.解析data
            JsonNode data = jsonNode.get("data");
            //3.1.查看data中是否含有status
            if (data.has("status")) {
                JsonNode statusChange = data.get("status");
                if (statusChange.has("old") && statusChange.has("new")) {
                    String oldStatus = statusChange.get("old").asText();
                    String newStatus = statusChange.get("new").asText();
                    if (LocalConstant.DeviceStatus.OFFLINE.equals(oldStatus) && LocalConstant.DeviceStatus.ONLINE.equals(newStatus)) {
                        String peerId = jsonNode.get("peerId").asText();
                        Device device = deviceService.getCurrentDevice(peerId);
                        if (device != null && LocalConstant.DeviceType.R.equals(device.getType())) {
                            log.error("parseMessageAndGetPeerId status from offline to online get peerId,peerId:[{}], message:{}", peerId, message);
                            //通知token服务
                            if (enableNotifyReceiverOnline) {
                                try {
                                    tokenFeignService.rStatusChangeCheckRPair(peerId);
                                } catch (Exception e) {
                                    log.error(e.toString(), e);
                                }
                            }
                            return device;
                        } else {
                            log.info("parseMessageAndGetPeerId check status failed device not R message:{} device:{}", message, device);
                        }
                    } else {
                        log.info("parseMessageAndGetPeerId check status failed---- status not old to new, message:{}", message);
                    }
                } else {
                    log.error("parseMessageAndGetPeerId check status failed----status no old or no new, message:{}", message);
                }
            }
            //3.2.如果含有IP，则获取IP
            if (data.has("ip")) {
                String peerId = jsonNode.get("peerId").asText();
                Device device = deviceService.getCurrentDevice(peerId);
                if (device != null && LocalConstant.DeviceType.R.equals(device.getType())) {
                    log.error("parseMessageAndGetPeerId ip changed get PeerId,peerId:[{}], message:{}", peerId, message);
                    //通知token服务
                    if (enableNotifyReceiverOnline) {
                        try {
                            tokenFeignService.rStatusChangeCheckRPair(peerId);
                        } catch (Exception e) {
                            log.error(e.toString(), e);
                        }
                    }
                    return device;
                } else {
                    log.error("parseMessageAndGetPeerId ip changed but device is null or device not R or ip is empty,device:[{}], message:{}", device, message);
                }
            }
        } catch (IOException e) {
            log.error("parseMessageAndGetPeerId>>>>>>>>parseMessage exception,message:[{}],exception:[{}]", message, e);
        }
        return null;
    }


    @Override
    public void refreshPirPortMappingCache() {
        List<PidPortMapping> pidPortMappings = super.selectExistAll();
        if (!CollectionUtils.isEmpty(pidPortMappings)) {
            Map<String, PidPortMapping> collect = pidPortMappings.stream().collect(Collectors.toMap(PidPortMapping::getPeerId, Function.identity(), (s1, s2) -> s2));
            PID_PORT_MAPPING_CACHE_MAP.putAll(collect);
        }
    }

    @Override
    public void refreshIpAndDomain(String peerId, String  email) {
        //1.根据peerId获取设备
        Device device = deviceService.getCurrentDevice(peerId);
        if (device == null) {
            throw new ServerException(ResultConstant.Device.DEVICE_NOT_EXIST);
        }
        if (StringUtils.isEmpty(device.getIp())) {
            throw new ServerException(ResultConstant.Device.DEVICE_IP_IS_EMPTY);
        }
        if (!StringUtils.equalsIgnoreCase(device.getType(), LocalConstant.DeviceType.R)) {
            throw new ServerException(ResultConstant.Device.DEVICE_IS_NOT_R);
        }
        //2.根据peerId获取pidPortMapping
        PidPortMapping pidPortMapping = this.getPortMappingCacheByPeerId(peerId, device.getIp());
        //3.更新
        String result = this.ipChanged(peerId, device.getIp(), pidPortMapping, "Page Manual Refresh: " + email, Boolean.TRUE);
        if (StringUtils.equalsIgnoreCase("releaseFailed", result)) {
            throw new ServerException(ResultConstant.PidPortMapping.RELEASE_DOMAIN_RECORD_FAILED);
        }
        if (StringUtils.isEmpty(result)) {
            throw new ServerException(ResultConstant.PidPortMapping.ADD_DOMAIN_RECORD_FAILED);
        }
    }

    @Override
    public PidPortMappingVo getByPeerId(String peerId) {
        PidPortMappingVo pidPortMappingVo = new PidPortMappingVo();
        //1.根据peerId获取设备
        Device device = deviceService.getCurrentDevice(peerId);
        if (device == null) {
            log.error("getByPeerId>>>>>>device is null peerId:[{}]", peerId);
            throw new ServerException(ResultConstant.Device.DEVICE_NOT_EXIST);
        }
        if (!StringUtils.equalsIgnoreCase(device.getType(), LocalConstant.DeviceType.R)) {
            log.error("getByPeerId>>>>>>device is not receiver. peerId:[{}]", peerId);
            throw new ServerException(ResultConstant.Device.DEVICE_IS_NOT_R);
        }
        String port = StringUtils.isNotEmpty(device.getPort()) ? String.valueOf(Integer.parseInt(device.getPort()) + 200) : "8288";
        String version = device.getVersion();
        double versionDouble = 0.0;
        if (StringUtils.isNotEmpty(version)) {
            versionDouble = Double.parseDouble(version.length() > 3 ? version.substring(0, 3) : version);
        }
        if (versionDouble >= 7.5) {
            port = this.getNginxPortByPeerId(peerId);
        }
        if (StringUtils.isEmpty(port)) {
            log.error("pppppppppppppppppp>port is empty ,peerId:[{}],device:[{}],", peerId, device);
            port = "8288";
        }
        PidPortMapping pidPortMapping = pidPortMappingMapper.getByPeerId(peerId);
        if (pidPortMapping == null || StringUtils.isEmpty(pidPortMapping.getSubDomain())) {
            log.error("getByPeerId>>>>>>pidPortMapping is empty. peerId:[{}]，pidPortMapping:[{}]", peerId, pidPortMapping);
            throw new ServerException(ResultConstant.PidPortMapping.DOMAIN_IS_EMPTY);
        }
        BeanUtils.copyProperties(pidPortMapping, pidPortMappingVo);
        pidPortMappingVo.setSubDomain(pidPortMapping.getSubDomain() + ":" + port + this.cloudWebRSuffix);
        return pidPortMappingVo;
    }

    /**
     * 根据peerId获取nginxPort
     * @param peerId  peerId
     * @return String
     */
    public String getNginxPortByPeerId(String peerId) {
        //2.调用receive接口获取port
        //2.1.获取NginxLocalPort信息
        Result<String> queryReceiverInfoResult = receiverControlService.command(peerId, CommandConstant.QUERY_RECEIVER_INFO, null);
        if (!queryReceiverInfoResult.assertSuccess()) {
            log.error("getByPeerId>>>>>>>get NginxLocalPort port failed,peerId:[{}],result:[{}]", peerId, queryReceiverInfoResult);
            throw new ServerException(queryReceiverInfoResult);
        }
        //2.2.解析结果，获取NginxLocalPort信息
        String queryReceiverInfo = queryReceiverInfoResult.getResult();
        JsonNode jsonTree = null;
        try {
            jsonTree = MyUtil.MAPPER.readTree(queryReceiverInfo);
        } catch (IOException e) {
            log.error("getByPeerId>>>>>>parse result exception,peerId:[{}],result:[{}],e:[{}]", peerId, queryReceiverInfoResult, e);
            throw new ServerException(ResultConstant.TimeLock.RECEIVER_QUERY_INFO_DATA_ERROR);
        }
        //2.3.获取NginxLocalPort
        JsonNode localIPAddresses = jsonTree.get("NginxLocalPort");
        if (localIPAddresses == null || StringUtils.isEmpty(localIPAddresses.asText())) {
            log.error("getByPeerId>>>>>>NginxLocalPort is empty,peerId:[{}],result:[{}],NginxLocalPort:[{}]",
                    peerId, queryReceiverInfoResult, localIPAddresses);
            throw new ServerException(ResultConstant.PidPortMapping.RECEIVER_CONFIG_NOT_EXIST_NGINX_LOCAL_PORT);
        }
        if (!StringUtils.equalsIgnoreCase(localIPAddresses.asText(), "8288")) {
            log.error("getByPeerId>>>>>>NginxLocalPort is not 8288,peerId:[{}],result:[{}],NginxLocalPort:[{}]",
                    peerId, queryReceiverInfoResult, localIPAddresses);
        }
        return localIPAddresses.asText();
    }

    /**
     *根据前缀+peerId+后缀来
     * @param peerId 设备的id
     * @return 三级域名
     */
    private String getDomainByPeerId(String peerId) {
        //1.peerId进行md5加密
        peerId = this.domainPrefix + peerId.toUpperCase() + this.domainSuffix;
        log.error("getDomainByPeerIdAndIp>>>>>peerId:[{}]", peerId);
        return StringUtil.MD5(peerId);
    }

    /**
     * 根据peerId获取PidPortMapping
     * @param peerId peerId
     * @return 返回pidPortMapping
     */
    private PidPortMapping getPortMappingCacheByPeerId(String peerId, String ip) {
        String redisKey = "pid:por:mapping:insert:" + peerId;
        try {
            redisLock.lock(redisKey);
            //1.如果缓存为空，则更新缓存
            if (PID_PORT_MAPPING_CACHE_MAP.size() == 0) {
               this.refreshPirPortMappingCache();
            }
            //2.从缓存中获取pidPortMapping
            PidPortMapping pidPortMapping = PID_PORT_MAPPING_CACHE_MAP.get(peerId);
            //3.如果为空则添加到t_pid_port_mapping表中
            if (pidPortMapping == null) {
                pidPortMapping = pidPortMappingMapper.getByPeerId(peerId);
                if (pidPortMapping == null) {
                    pidPortMapping = new PidPortMapping()
                            .setPeerId(peerId)
                            .setIp(ip);
                    super.insert(pidPortMapping);
                }
                PID_PORT_MAPPING_CACHE_MAP.put(peerId, pidPortMapping);
            }
            return pidPortMapping;
        } finally {
            redisLock.unlock(redisKey);
        }
    }
}
